package ar.edu.unlu.sistemas.p2p.business.peer;

import java.net.InetSocketAddress;
import java.net.SocketAddress;

import ar.edu.unlu.sistemas.p2p.business.msg.Message;
import ar.edu.unlu.sistemas.p2p.business.msg.MessageFactory;
import ar.edu.unlu.sistemas.p2p.business.msg.PeerConnectionACKMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.PeerDisconnectionACKMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.PeerDisconnectionMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.PingMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.PongMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.QueryMessage;
import ar.edu.unlu.sistemas.p2p.business.msg.QueryResponseMessage;
import ar.edu.unlu.sistemas.p2p.business.net.tcp.PeerTCPConnection;
import ar.edu.unlu.sistemas.p2p.business.util.ByteHelper;
import ar.edu.unlu.sistemas.p2p.business.util.StringUtils;

public class Peer {
	
	private String id;
	private InetSocketAddress address;
	private PeerTCPConnection connection;
	private String name;
	
	/*public Peer(String id, PeerTCPConnection connection) {
		this.id = id;
		this.connection = connection;
		this.address = (InetSocketAddress) connection.getSocket().getRemoteSocketAddress();
		this.connection.setPeer(this);
	}*/
	
	public Peer() {
	}
	
	public Peer(String id, SocketAddress address) {
		this.address = (InetSocketAddress) address;
		this.id = id;
	}
	
	public Peer(SocketAddress address, String macAddr) {
		this.address = (InetSocketAddress) address;
		this.generateId(macAddr);
	}
	
	public Peer(String name, PeerTCPConnection connection, String macAddr) {
		this.name = name;
		this.address = (InetSocketAddress) connection.getSocket().getRemoteSocketAddress();
		this.connection = connection;
		this.generateId(macAddr);
		connection.setPeer(this);
	}
	
	public Peer(PeerTCPConnection connection, String name, String id) {
		this.name = name;
		this.address = (InetSocketAddress) connection.getSocket().getRemoteSocketAddress();
		this.connection = connection;
		this.id = id;
		connection.setPeer(this);
	}
	
	public Peer(String name, int listenPort, PeerTCPConnection connection, String id) {
		this.name = name;
		InetSocketAddress addr = (InetSocketAddress)connection.getSocket().getRemoteSocketAddress();
		this.address = new InetSocketAddress(addr.getAddress(), listenPort);
		this.connection = connection;
		this.id = id;
		connection.setPeer(this);
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public PeerTCPConnection getConnection() {
		return connection;
	}

	public void setConnection(PeerTCPConnection connection) {
		this.connection = connection;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public InetSocketAddress getAddress() {
		return address;
	}

	public void setAddress(SocketAddress address) {
		this.address = (InetSocketAddress) address;
	}
	
	/**
	 * Genera el id del Peer a través de la dirección ip:puerto de escucha que lo representa.
	 * Se utiliza sólo localmente
	 * 
	 */
	private void generateId(String macAddr) {
		byte[] byteId = new byte[6];
		InetSocketAddress address = (InetSocketAddress) this.getAddress();
		byte[] addr = address.getAddress().getAddress();
		for (int i = 0;i<addr.length;i++){
			byteId[i] = addr[i];
		}
		int port = address.getPort();
		ByteHelper.intNumIntoByteArr(port, byteId, 4);
		StringBuffer sb = new StringBuffer();
		sb.append(ByteHelper.getHex(byteId));
		sb.append(StringUtils.parseMacAddress(macAddr));
		this.id = sb.toString();
	}
	
	public void disconnect(){
		this.connection.close();
	}
	
	public String toString() {
		return "id: "+this.getId()+" | name: "+(this.getName()!=null?this.getName():"")+" | addr: "+this.getAddress().toString();
	}
	
	/**
	 * Envia la respuesta finalzando el handshake de conexion al peer representado por este objeto
	 */
	public void sendAcceptConnection() {
		PeerConnectionACKMessage msg =  (PeerConnectionACKMessage) MessageFactory.getMessage(MessageFactory.CODE_CONNECT_PEER_NODE_ACK);
		msg.setPeerName(PeerController.getInstance().getLocalPeer().getName());
		msg.setPeerId(PeerController.getInstance().getLocalPeer().getId());
		this.getConnection().send(msg.toXMLString());
	}
	
	/**
	 * Envia la respuesta finalzando el handshake de desconexion al peer representado por este objeto
	 */
	public void sendRequestDisconnection() {
		PeerDisconnectionMessage msg = (PeerDisconnectionMessage) MessageFactory.getMessage(MessageFactory.CODE_DISCONNECT_PEER_NODE);
		this.getConnection().send(msg.toXMLString());
	}
	
	/**
	 * Envia la respuesta finalzando el handshake de desconexion al peer representado por este objeto
	 */
	public void sendAcceptDisconnection() {
		PeerDisconnectionACKMessage msg = (PeerDisconnectionACKMessage) MessageFactory.getMessage(MessageFactory.CODE_DISCONNECT_PEER_NODE_ACK);
		this.getConnection().send(msg.toXMLString());
	}
	
	/**
	 * Le envia a este peer un mensaje de ping generado por el peer representado por la aplicación
	 * 
	 * @param msgId
	 */
	public void sendPing(String msgId) {
		PingMessage msg = (PingMessage) MessageFactory.getMessage(MessageFactory.CODE_PING);
		msg.getHeader().setMsgId(msgId);
		this.getConnection().send(msg.toXMLString());
	}
	
	/**
	 * Reenvia un mensaje enviado por parametro. Decrementa el TTL
	 * 
	 * @param msg
	 */
	public void forward(Message msg) {
		msg.getHeader().setTtl(msg.getHeader().getTtl()-1);
		msg.getHeader().setHops(msg.getHeader().getHops()+1);
		this.getConnection().send(msg.toXMLString());
	}
	
	/**
	 * Se encargar de enviar el mensaje pong pasado por parametro al peer representado por este objeto
	 * 
	 * @param msg
	 */
	public void sendPong(PongMessage msg) {
		this.getConnection().send(msg.toXMLString());
	}
	
	/**
	 * Se encarga de enviar un mensaje Pong sin resultados al peer manejado por este objeto
	 * 
	 * @param msgId
	 */
	public void sendEmptyPong(String msgId) {
		PongMessage msg = (PongMessage) MessageFactory.getMessage(MessageFactory.CODE_PONG);
		msg.getHeader().setMsgId(msgId);
		this.getConnection().send(msg.toXMLString());
	}
	
	/**
	 * Se encarga de enviar un mensaje query al peer manejado por este objeto
	 * 
	 * @param msgId
	 * @param la query representada en un String
	 */
	public void sendQuery(String msgId, String query) {
		QueryMessage msg = (QueryMessage) MessageFactory.getMessage(MessageFactory.CODE_QUERY);
		msg.getHeader().setMsgId(msgId);
		msg.setQuery(query);
		this.getConnection().send(msg.toXMLString());
	}
	
	/**
	 * Se encarga de enviar un mensaje de respuesta de query al peer manejado por este objeto
	 * 
	 * @param el mensaje de respuesta a la query
	 */
	public void sendQueryResponse(QueryResponseMessage msg) {
		this.getConnection().send(msg.toXMLString());
	}
	
	/**
	 * Se encarga de enviar un mensaje Pong sin resultados al peer manejado por este objeto
	 * 
	 * @param msgId
	 */
	public void sendEmptyQueryResponse(String msgId) {
		QueryResponseMessage msg = (QueryResponseMessage) MessageFactory.getMessage(MessageFactory.CODE_QUERY_RESP);
		msg.getHeader().setMsgId(msgId);
		this.getConnection().send(msg.toXMLString());
	}

}
